package access

import (
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"sync"

	"chainmaker.org/chainmaker/common/v2/concurrentlru"
	bcx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
	"chainmaker.org/chainmaker/common/v2/msgbus"
	pbac "chainmaker.org/chainmaker/pb-go/v2/accesscontrol"
	"chainmaker.org/chainmaker/pb-go/v2/common"
	"chainmaker.org/chainmaker/pb-go/v2/config"
	"chainmaker.org/chainmaker/protocol/v2"

	nodeconfig "github.com/tokentransfer/node/config"
	"github.com/tokentransfer/node/util"
)

type acProvider struct {
	config *nodeconfig.Config

	orgMap *sync.Map // map[string]interface{} , orgId -> interface{}

	//local cache for member
	memberCache *concurrentlru.Cache

	// local cache for certificates (reduce the size of block)
	certCache *concurrentlru.Cache

	// local cache for certificate revocation list and frozen list
	crl        sync.Map
	frozenList sync.Map

	localOrg     *organization
	trustMembers *sync.Map
	opts         bcx509.VerifyOptions
}

type trustMemberCached struct {
	trustMember *nodeconfig.TrustMemberConfig
	cert        *bcx509.Certificate
}

type memberCached struct {
	member    protocol.Member
	certChain []*bcx509.Certificate
}

var _ protocol.AccessControlProvider = (*acProvider)(nil)

func NewACProvider(c *nodeconfig.Config, localOrgId string, log protocol.Logger, msgBus msgbus.MessageBus) (protocol.AccessControlProvider, error) {
	provider, err := newACProvider(c, localOrgId, log)
	if err != nil {
		return nil, err
	}
	msgBus.Register(msgbus.ChainConfig, provider)
	return provider, nil
}

func newACProvider(c *nodeconfig.Config, localOrgId string, log protocol.Logger) (*acProvider, error) {
	provider := &acProvider{
		config: c,

		orgMap:      &sync.Map{},
		memberCache: concurrentlru.New(c.NodeConfig.CertCacheSize),
		certCache:   concurrentlru.New(c.NodeConfig.CertCacheSize),
		crl:         sync.Map{},
		frozenList:  sync.Map{},

		localOrg:     nil,
		trustMembers: &sync.Map{},
		opts: bcx509.VerifyOptions{
			Intermediates: bcx509.NewCertPool(),
			Roots:         bcx509.NewCertPool(),
		},
	}
	err := provider.initTrustMembers(c.NodeConfig.TrustMembers)
	if err != nil {
		return nil, err
	}
	err = provider.initTrustRoots(c.NodeConfig.TrustRoots)
	if err != nil {
		return nil, err
	}
	if len(localOrgId) > 0 {
		localOrg := provider.getOrgInfoByOrgId(localOrgId)
		if localOrg == nil {
			return nil, util.ErrorOfNotFound("organization", localOrgId)
		}
		provider.localOrg, _ = localOrg.(*organization)
	}
	return provider, nil
}

// GetHashAlg return hash algorithm the access control provider uses
func (cp *acProvider) GetHashAlg() string {
	return cp.config.NodeConfig.HashType
}

// ValidateResourcePolicy checks whether the given resource policy is valid
func (cp *acProvider) ValidateResourcePolicy(resourcePolicy *config.ResourcePolicy) bool {
	return false
}

// LookUpPolicy returns corresponding policy configured for the given resource name
func (cp *acProvider) LookUpPolicy(resourceName string) (*pbac.Policy, error) {
	return nil, nil
}

// LookUpExceptionalPolicy returns corresponding exceptional policy configured for the given resource name
func (cp *acProvider) LookUpExceptionalPolicy(resourceName string) (*pbac.Policy, error) {
	return nil, nil
}

//GetAllPolicy returns all policies
func (cp *acProvider) GetAllPolicy() (map[string]*pbac.Policy, error) {
	return nil, nil
}

// CreatePrincipal creates a principal for one time authentication
func (cp *acProvider) CreatePrincipal(resourceName string, endorsements []*common.EndorsementEntry, message []byte) (protocol.Principal, error) {
	return nil, nil
}

// CreatePrincipalForTargetOrg creates a principal for "SELF" type policy,
// which needs to convert SELF to a sepecific organization id in one authentication
func (cp *acProvider) CreatePrincipalForTargetOrg(resourceName string, endorsements []*common.EndorsementEntry, message []byte,
	targetOrgId string) (protocol.Principal, error) {
	return nil, nil
}

//GetValidEndorsements filters all endorsement entries and returns all valid ones
func (cp *acProvider) GetValidEndorsements(principal protocol.Principal) ([]*common.EndorsementEntry, error) {
	return nil, nil
}

// VerifyPrincipal verifies if the policy for the resource is met
func (cp *acProvider) VerifyPrincipal(principal protocol.Principal) (bool, error) {
	return false, nil
}

// NewMember creates a member from pb Member
func (cp *acProvider) NewMember(pbMember *pbac.Member) (protocol.Member, error) {
	var memberTmp *pbac.Member
	if pbMember.MemberType != pbac.MemberType_CERT &&
		pbMember.MemberType != pbac.MemberType_ALIAS &&
		pbMember.MemberType != pbac.MemberType_CERT_HASH {
		return nil, fmt.Errorf("new member failed: the member type does not match")
	}
	if pbMember.MemberType == pbac.MemberType_CERT_HASH || pbMember.MemberType == pbac.MemberType_ALIAS {
		memInfoBytes, ok := cp.lookUpCertCache(pbMember.MemberInfo)
		if !ok {
			return nil, fmt.Errorf("new member failed, the provided certificate ID is not registered")
		}
		memberTmp = &pbac.Member{
			OrgId:      pbMember.OrgId,
			MemberType: pbMember.MemberType,
			MemberInfo: memInfoBytes,
		}
	} else {
		memberTmp = pbMember
	}
	memberCache, ok := cp.lookUpMemberInCache(string(memberTmp.MemberInfo))
	if !ok {
		remoteMember, isTrustMember, err := cp.newNoCacheMember(memberTmp)
		if err != nil {
			return nil, fmt.Errorf("new member failed: %s", err.Error())
		}
		var certChain []*bcx509.Certificate
		if !isTrustMember {
			certChain, err = cp.verifyMember(remoteMember)
			if err != nil {
				return nil, fmt.Errorf("new member failed: %s", err.Error())
			}
		}
		cp.memberCache.Add(string(memberTmp.MemberInfo), &memberCached{
			member:    remoteMember,
			certChain: certChain,
		})
		return remoteMember, nil
	}
	return memberCache.member, nil
}

//GetMemberStatus get the status information of the member
func (cp *acProvider) GetMemberStatus(pbMember *pbac.Member) (pbac.MemberStatus, error) {
	member, err := cp.NewMember(pbMember)
	if err != nil {
		return pbac.MemberStatus_INVALID, err
	}

	var certChain []*bcx509.Certificate
	cert := member.(*certificateMember).cert

	certChain = append(certChain, cert)
	isRevoked, err := cp.checkCRL(certChain)
	if err != nil && isRevoked {
		return pbac.MemberStatus_REVOKED, err
	}
	isFrozen, err := cp.checkCertFrozenList(certChain)
	if err != nil && isFrozen {
		return pbac.MemberStatus_FROZEN, err
	}
	return pbac.MemberStatus_NORMAL, nil
}

//VerifyRelatedMaterial verify the member's relevant identity material
func (cp *acProvider) VerifyRelatedMaterial(verifyType pbac.VerifyType, data []byte) (bool, error) {
	return false, nil
}

func (cp *acProvider) OnMessage(msg *msgbus.Message) {

}

func (cp *acProvider) OnQuit() {

}

func (cp *acProvider) checkCRL(certChain []*bcx509.Certificate) (bool, error) {
	if len(certChain) < 1 {
		return false, fmt.Errorf("given certificate chain is empty")
	}
	for _, cert := range certChain {
		akiCert := cert.AuthorityKeyId

		crl, ok := cp.crl.Load(string(akiCert))
		if ok {
			// we have ac CRL, check whether the serial number is revoked
			for _, rc := range crl.(*pkix.CertificateList).TBSCertList.RevokedCertificates {
				if rc.SerialNumber.Cmp(cert.SerialNumber) == 0 {
					return true, errors.New("certificate is revoked")
				}
			}
		}
	}
	return false, nil
}

func (cp *acProvider) checkCertFrozenList(certChain []*bcx509.Certificate) (bool, error) {
	if len(certChain) < 1 {
		return false, fmt.Errorf("given certificate chain is empty")
	}
	_, ok := cp.frozenList.Load(string(certChain[0].Raw))
	if ok {
		return true, fmt.Errorf("certificate is frozen")
	}
	return false, nil
}

// Check whether the provided member is a valid member of this group
func (cp *acProvider) verifyMember(mem protocol.Member) ([]*bcx509.Certificate, error) {
	if mem == nil {
		return nil, fmt.Errorf("invalid member: member should not be nil")
	}
	certMember, ok := mem.(*certificateMember)
	if !ok {
		return nil, fmt.Errorf("invalid member: member type err")
	}
	orgIdFromCert := certMember.cert.Subject.Organization[0]
	org := cp.getOrgInfoByOrgId(orgIdFromCert)
	// the Third-party CA
	if certMember.cert.IsCA && org == nil {
		certChain := []*bcx509.Certificate{certMember.cert}
		_, err := cp.checkCRL(certChain)
		if err != nil {
			return nil, err
		}
		_, err = cp.checkCertFrozenList(certChain)
		if err != nil {
			return nil, err
		}
		return certChain, nil
	}
	if mem.GetOrgId() != orgIdFromCert {
		return nil, fmt.Errorf(
			"signer does not belong to the organization it claims [claim: %s, certificate: %s]",
			mem.GetOrgId(),
			orgIdFromCert,
		)
	}
	if org == nil {
		return nil, fmt.Errorf("no orgnization found")
	}
	certChains, err := certMember.cert.Verify(cp.opts)
	if err != nil {
		return nil, fmt.Errorf("not ac valid certificate from trusted CAs: %v", err)
	}
	if len(org.(*organization).trustedRootCerts) <= 0 {
		return nil, fmt.Errorf("no trusted root: please configure trusted root certificate")
	}
	certChain := cp.findCertChain(org.(*organization), certChains)
	if certChain != nil {
		return certChain, nil
	}
	return nil, fmt.Errorf("authentication failed, signer does not belong to the organization it claims"+
		" [claim: %s]", mem.GetOrgId())
}

func (cp *acProvider) findCertChain(org *organization, certChains [][]*bcx509.Certificate) []*bcx509.Certificate {
	for _, chain := range certChains {
		rootCert := chain[len(chain)-1]
		_, ok := org.trustedRootCerts[string(rootCert.Raw)]
		if ok {
			var err error
			// check CRL and frozen list
			_, err = cp.checkCRL(chain)
			if err != nil {
				continue
			}
			_, err = cp.checkCertFrozenList(chain)
			if err != nil {
				continue
			}
			return chain
		}
	}
	return nil
}

func (cp *acProvider) loadTrustMembers(memberInfo string) (*trustMemberCached, bool) {
	cached, ok := cp.trustMembers.Load(string(memberInfo))
	if ok {
		return cached.(*trustMemberCached), ok
	}
	return nil, ok
}

func (cp *acProvider) newNoCacheMember(pbMember *pbac.Member) (member protocol.Member,
	isTrustMember bool, err error) {
	cached, ok := cp.loadTrustMembers(string(pbMember.MemberInfo))
	if ok {
		var isCompressed bool
		if pbMember.MemberType == pbac.MemberType_CERT {
			isCompressed = false
		}
		var certMember *certificateMember
		certMember, err = newCertMemberFromParam(cached.trustMember.OrgId, cached.trustMember.Role,
			cp.config.NodeConfig.HashType, isCompressed, []byte(cached.trustMember.MemberInfo))
		if err != nil {
			return nil, isTrustMember, err
		}
		isTrustMember = true
		return certMember, isTrustMember, nil
	}
	member, err = newCertMemberFromPb(pbMember, cp.config)
	if err != nil {
		return nil, isTrustMember, fmt.Errorf("new member failed: %s", err.Error())
	}
	return member, isTrustMember, nil
}

func (cp *acProvider) lookUpMemberInCache(memberInfo string) (*memberCached, bool) {
	ret, ok := cp.memberCache.Get(memberInfo)
	if ok {
		return ret.(*memberCached), true
	}
	return nil, false
}

func (cp *acProvider) addMemberToCache(memberInfo string, member *memberCached) {
	cp.memberCache.Add(memberInfo, member)
}

// lookUpCertCache Cache for compressed certificate
func (cp *acProvider) lookUpCertCache(certId []byte) ([]byte, bool) {
	ret, ok := cp.certCache.Get(string(certId))
	if ok && ret != nil {
		return ret.([]byte), true
	}
	return nil, false
}

func (cp *acProvider) addOrg(orgId string, orgInfo interface{}) {
	_, loaded := cp.orgMap.LoadOrStore(orgId, orgInfo)
	if loaded {
		cp.orgMap.Store(orgId, orgInfo)
	}
}

func (cp *acProvider) getOrgInfoByOrgId(orgId string) interface{} {
	orgInfo, ok := cp.orgMap.Load(orgId)
	if !ok {
		return nil
	}
	return orgInfo
}

func (cp *acProvider) initTrustRoots(roots []nodeconfig.TrustRootConfig) error {
	for _, orgRoot := range roots {
		org := &organization{
			id:                       orgRoot.OrgId,
			trustedRootCerts:         map[string]*bcx509.Certificate{},
			trustedIntermediateCerts: map[string]*bcx509.Certificate{},
		}
		for _, root := range orgRoot.Roots {
			rootBytes, err := ioutil.ReadFile(root)
			if err != nil {
				return err
			}
			certificateChain, err := cp.buildCertificateChain(rootBytes, orgRoot.OrgId, org)
			if err != nil {
				return err
			}
			if certificateChain == nil || !certificateChain[len(certificateChain)-1].IsCA {
				return fmt.Errorf("the certificate configured as root for organization %s is not a CA certificate", orgRoot.OrgId)
			}
			org.trustedRootCerts[string(certificateChain[len(certificateChain)-1].Raw)] =
				certificateChain[len(certificateChain)-1]
			cp.opts.Roots.AddCert(certificateChain[len(certificateChain)-1])
			for i := 0; i < len(certificateChain); i++ {
				org.trustedIntermediateCerts[string(certificateChain[i].Raw)] = certificateChain[i]
				cp.opts.Intermediates.AddCert(certificateChain[i])
			}

			if len(org.trustedRootCerts) <= 0 {
				return fmt.Errorf(
					"setup organization failed, no trusted root (for %s): "+
						"please configure trusted root certificate or trusted public key whitelist",
					orgRoot.OrgId,
				)
			}
		}
		cp.addOrg(orgRoot.OrgId, org)
	}
	return nil
}

func (cp *acProvider) buildCertificateChain(rootData []byte, orgId string, org *organization) ([]*bcx509.Certificate, error) {
	var certificates, certificateChain []*bcx509.Certificate
	pemBlock, rest := pem.Decode([]byte(rootData))
	for pemBlock != nil {
		cert, errCert := bcx509.ParseCertificate(pemBlock.Bytes)
		if errCert != nil || cert == nil {
			return nil, fmt.Errorf("invalid entry int trusted root cert list")
		}
		if len(cert.Signature) == 0 {
			return nil, fmt.Errorf("invalid certificate [SN: %s]", cert.SerialNumber)
		}
		certificates = append(certificates, cert)
		pemBlock, rest = pem.Decode(rest)
	}
	certificateChain = bcx509.BuildCertificateChain(certificates)
	return certificateChain, nil
}

func (cp *acProvider) initTrustMembers(trustMembers []nodeconfig.TrustMemberConfig) error {
	var syncMap sync.Map
	for _, member := range trustMembers {
		certBlock, _ := pem.Decode([]byte(member.MemberInfo))
		if certBlock == nil {
			return fmt.Errorf("init trust members failed, none certificate given, memberInfo:[%s]",
				member.MemberInfo)
		}
		trustMemberCert, err := bcx509.ParseCertificate(certBlock.Bytes)
		if err != nil {
			return fmt.Errorf("init trust members failed, parse certificate failed, memberInfo:[%s]",
				member.MemberInfo)
		}
		cached := &trustMemberCached{
			trustMember: &member,
			cert:        trustMemberCert,
		}
		syncMap.Store(member.MemberInfo, cached)
	}
	cp.trustMembers = &syncMap

	return nil
}
